<template>
  <div class="puzzle-captcha-container">
    <!-- 遮罩层 -->
    <div class="puzzle-captcha-mask" v-if="showMask" @click="handleMaskClick"></div>

    <!-- 拼图验证框 -->
    <div class="puzzle-captcha-box" :class="{ 'is-loading': isLoading }">
      <!-- 标题 -->
      <div class="puzzle-captcha-header">
        <h3 class="puzzle-captcha-title">请完成安全验证</h3>
        <button class="puzzle-captcha-refresh" @click="refreshPuzzle" :disabled="isLoading || isVerifying">
          <i class="fa fa-refresh"></i>
        </button>
      </div>

      <!-- 拼图区域 -->
      <div class="puzzle-captcha-puzzle">
        <!-- 背景图片 -->
        <div
            class="puzzle-captcha-bg"
            :style="{
            backgroundImage: `url(${bgImage})`,
            filter: isSuccess ? 'blur(0)' : 'blur(2px)'
          }"
        >
          <!-- 拼图块 -->
          <div
              class="puzzle-captcha-piece"
              :style="{
              left: `${pieceLeft}px`,
              top: `${pieceTop}px`,
              transform: `translateX(${sliderValue}px)`,
              backgroundImage: `url(${bgImage})`,
              backgroundPosition: `-${pieceLeft}px -${pieceTop}px`,
              boxShadow: isSuccess ? '0 0 15px rgba(0, 255, 0, 0.8)' : '0 0 15px rgba(0, 0, 0, 0.2)'
            }"
          >
            <div class="puzzle-captcha-piece-inner"></div>
          </div>

          <!-- 拼图缺口 -->
          <div
              class="puzzle-captcha-hole"
              :style="{
              left: `${holeLeft}px`,
              top: `${holeTop}px`,
              boxShadow: isSuccess ? '0 0 15px rgba(0, 255, 0, 0.8)' : '0 0 15px rgba(0, 0, 0, 0.2)'
            }"
          >
            <div class="puzzle-captcha-hole-inner"></div>
          </div>
        </div>

        <!-- 滑块区域 -->
        <div class="puzzle-captcha-slider">
          <div
              class="puzzle-captcha-slider-track"
              :style="{ width: `${sliderValue / sliderMax * 100}%` }"
          ></div>
          <div
              class="puzzle-captcha-slider-handle"
              :style="{ left: `${sliderValue}px` }"
              @mousedown="handleMouseDown"
              @touchstart="handleTouchStart"
          >
            <i class="fa fa-chevron-right"></i>
          </div>
          <div class="puzzle-captcha-slider-text">
            {{ sliderText }}
          </div>
        </div>

        <!-- 验证状态提示 -->
        <div class="puzzle-captcha-status" v-if="statusText">
          <span class="puzzle-captcha-status-icon" :class="{ 'fa-check-circle': isSuccess, 'fa-times-circle': isError }">
            <i class="fa" :class="{ 'fa-check': isSuccess, 'fa-times': isError }"></i>
          </span>
          <span class="puzzle-captcha-status-text">{{ statusText }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'PuzzleCaptcha',
  props: {
    // 背景图片URL
    imageUrl: {
      type: String,
      default: 'https://picsum.photos/400/200'
    },
    // 拼图块大小
    pieceSize: {
      type: Number,
      default: 40
    },
    // 验证成功回调
    onSuccess: {
      type: Function,
      default: () => {}
    },
    // 验证失败回调
    onError: {
      type: Function,
      default: () => {}
    },
    // 初始是否显示
    visible: {
      type: Boolean,
      default: true
    },
    // 滑块容错率
    tolerance: {
      type: Number,
      default: 5
    },
    // 拼图难度系数(1-5)
    difficulty: {
      type: Number,
      default: 3,
      validator: value => value >= 1 && value <= 5
    }
  },
  data() {
    return {
      // 背景图片URL
      bgImage: '',
      // 拼图块位置
      pieceLeft: 0,
      pieceTop: 0,
      // 拼图缺口位置
      holeLeft: 0,
      holeTop: 0,
      // 滑块位置
      sliderValue: 0,
      // 滑块最大值
      sliderMax: 300,
      // 滑块文本
      sliderText: '向右滑动完成验证',
      // 验证状态文本
      statusText: '',
      // 是否成功
      isSuccess: false,
      // 是否错误
      isError: false,
      // 是否正在验证
      isVerifying: false,
      // 是否正在加载
      isLoading: false,
      // 鼠标/触摸状态
      isDragging: false,
      // 显示遮罩层
      showMask: this.visible
    };
  },
  watch: {
    // 监听visible属性变化
    visible(newVal) {
      this.showMask = newVal;
      if (newVal) {
        this.refreshPuzzle();
      }
    }
  },
  mounted() {
    // 初始化拼图
    if (this.visible) {
      this.refreshPuzzle();
    }
  },
  methods: {
    // 初始化拼图数据
    initPuzzle() {
      this.isLoading = true;
      this.isSuccess = false;
      this.isError = false;
      this.statusText = '';
      this.sliderValue = 0;
      this.sliderText = '向右滑动完成验证';

      // 随机生成拼图块和缺口位置
      const maxLeft = this.sliderMax - this.pieceSize - 10;
      const maxTop = 120 - this.pieceSize - 10;

      // 根据难度调整位置随机性
      const difficultyFactor = this.difficulty / 5;

      // 生成拼图块位置(左侧1/3区域)
      this.pieceLeft = Math.floor(maxLeft * 0.3 * difficultyFactor) + 10;
      this.pieceTop = Math.floor(Math.random() * maxTop) + 10;

      // 生成缺口位置(右侧2/3区域)
      this.holeLeft = Math.floor(maxLeft * 0.7 * difficultyFactor) + maxLeft * 0.3 + 10;
      this.holeTop = Math.floor(Math.random() * maxTop) + 10;

      // 加载图片
      this.loadImage();
    },

    // 加载图片
    loadImage() {
      const img = new Image();
      img.onload = () => {
        this.bgImage = this.imageUrl;
        this.isLoading = false;
      };
      img.onerror = () => {
        // 图片加载失败，使用默认图片
        this.bgImage = 'https://picsum.photos/400/200';
        this.isLoading = false;
      };
      img.src = this.imageUrl;
    },

    // 刷新拼图
    refreshPuzzle() {
      this.initPuzzle();
    },

    // 处理鼠标按下事件
    handleMouseDown(e) {
      if (this.isSuccess || this.isError || this.isLoading || this.isVerifying) return;

      this.isDragging = true;
      this.startX = e.clientX;
      this.startValue = this.sliderValue;

      // 绑定事件
      document.addEventListener('mousemove', this.handleMouseMove);
      document.addEventListener('mouseup', this.handleMouseUp);
    },

    // 处理触摸开始事件
    handleTouchStart(e) {
      if (this.isSuccess || this.isError || this.isLoading || this.isVerifying) return;

      this.isDragging = true;
      this.startX = e.touches[0].clientX;
      this.startValue = this.sliderValue;

      // 绑定事件
      document.addEventListener('touchmove', this.handleTouchMove);
      document.addEventListener('touchend', this.handleTouchEnd);
    },

    // 处理鼠标移动事件
    handleMouseMove(e) {
      if (!this.isDragging) return;

      const deltaX = e.clientX - this.startX;
      this.sliderValue = Math.min(Math.max(this.startValue + deltaX, 0), this.sliderMax);

      // 更新滑块文本
      if (this.sliderValue > this.sliderMax * 0.8) {
        this.sliderText = '松开完成验证';
      } else {
        this.sliderText = '继续向右滑动';
      }
    },

    // 处理触摸移动事件
    handleTouchMove(e) {
      if (!this.isDragging) return;

      const deltaX = e.touches[0].clientX - this.startX;
      this.sliderValue = Math.min(Math.max(this.startValue + deltaX, 0), this.sliderMax);

      // 更新滑块文本
      if (this.sliderValue > this.sliderMax * 0.8) {
        this.sliderText = '松开完成验证';
      } else {
        this.sliderText = '继续向右滑动';
      }
    },

    // 处理鼠标释放事件
    handleMouseUp() {
      if (!this.isDragging) return;

      this.isDragging = false;
      this.verifyPuzzle();

      // 解绑事件
      document.removeEventListener('mousemove', this.handleMouseMove);
      document.removeEventListener('mouseup', this.handleMouseUp);
    },

    // 处理触摸结束事件
    handleTouchEnd() {
      if (!this.isDragging) return;

      this.isDragging = false;
      this.verifyPuzzle();

      // 解绑事件
      document.removeEventListener('touchmove', this.handleTouchMove);
      document.removeEventListener('touchend', this.handleTouchEnd);
    },

    // 验证拼图
    verifyPuzzle() {
      if (this.isSuccess || this.isError || this.isLoading) return;

      this.isVerifying = true;

      // 计算拼图块和缺口的水平距离
      const distance = Math.abs(this.sliderValue - (this.holeLeft - this.pieceLeft));

      // 模拟API验证延迟
      setTimeout(() => {
        if (distance <= this.tolerance) {
          // 验证成功
          this.handleSuccess();
        } else {
          // 验证失败
          this.handleError();
        }
        this.isVerifying = false;
      }, 300);
    },

    // 处理验证成功
    handleSuccess() {
      this.isSuccess = true;
      this.statusText = '验证成功';
      this.sliderText = '验证通过';

      // 触发成功回调
      this.onSuccess();

      // 3秒后自动关闭
      setTimeout(() => {
        this.showMask = false;
      }, 3000);
    },

    // 处理验证失败
    handleError() {
      this.isError = true;
      this.statusText = '验证失败，请重试';
      this.sliderText = '验证失败';

      // 触发失败回调
      this.onError();

      // 震动动画
      this.shakeSlider();

      // 2秒后重置
      setTimeout(() => {
        this.resetPuzzle();
      }, 2000);
    },

    // 重置拼图
    resetPuzzle() {
      this.isError = false;
      this.statusText = '';
      this.sliderValue = 0;
      this.sliderText = '向右滑动完成验证';
    },

    // 滑块震动动画
    shakeSlider() {
      const steps = [0, 10, -10, 10, -5, 5, -5, 0];
      let i = 0;

      const animate = () => {
        if (i < steps.length) {
          this.sliderValue += steps[i];
          i++;
          requestAnimationFrame(animate);
        }
      };

      animate();
    },

    // 处理遮罩层点击
    handleMaskClick() {
      this.showMask = false;
    }
  }
};
</script>

<style scoped>
.puzzle-captcha-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;
}

.puzzle-captcha-mask {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px);
  animation: fadeIn 0.3s ease-in-out;
}

.puzzle-captcha-box {
  position: relative;
  width: 400px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
  overflow: hidden;
  animation: zoomIn 0.3s ease-out;
}

.puzzle-captcha-box.is-loading {
  opacity: 0.7;
  pointer-events: none;
}

.puzzle-captcha-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 20px;
  background-color: #f5f7fa;
  border-bottom: 1px solid #e6e8eb;
}

.puzzle-captcha-title {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  color: #333;
}

.puzzle-captcha-refresh {
  background: none;
  border: none;
  color: #999;
  cursor: pointer;
  transition: color 0.2s;
  outline: none;
}

.puzzle-captcha-refresh:hover {
  color: #666;
}

.puzzle-captcha-refresh:disabled {
  color: #ccc;
  cursor: not-allowed;
}

.puzzle-captcha-puzzle {
  padding: 20px;
}

.puzzle-captcha-bg {
  position: relative;
  width: 360px;
  height: 160px;
  background-size: cover;
  background-position: center;
  border-radius: 4px;
  overflow: hidden;
  transition: filter 0.3s;
}

.puzzle-captcha-piece {
  position: absolute;
  width: 40px;
  height: 40px;
  background-size: 360px 160px;
  border-radius: 4px;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
  transition: transform 0.2s ease-out, box-shadow 0.3s;
  z-index: 2;
  cursor: grab;
}

.puzzle-captcha-piece:active {
  cursor: grabbing;
}

.puzzle-captcha-piece-inner {
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.3);
  border-radius: 4px;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 0, 25% 25%, 25% 75%, 75% 75%, 75% 25%, 25% 25%);
}

.puzzle-captcha-hole {
  position: absolute;
  width: 40px;
  height: 40px;
  border-radius: 4px;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
  transition: box-shadow 0.3s;
  z-index: 1;
}

.puzzle-captcha-hole-inner {
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.7);
  border-radius: 4px;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 0, 25% 25%, 25% 75%, 75% 75%, 75% 25%, 25% 25%);
}

.puzzle-captcha-slider {
  position: relative;
  margin-top: 20px;
  height: 40px;
  background-color: #f5f7fa;
  border-radius: 20px;
  overflow: hidden;
}

.puzzle-captcha-slider-track {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background-color: #409eff;
  transition: width 0.1s linear;
}

.puzzle-captcha-slider-handle {
  position: absolute;
  top: 0;
  left: 0;
  width: 40px;
  height: 40px;
  background-color: #fff;
  border-radius: 50%;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
  display: flex;
  justify-content: center;
  align-items: center;
  color: #409eff;
  cursor: grab;
  transition: left 0.1s linear, background-color 0.3s;
  z-index: 2;
}

.puzzle-captcha-slider-handle:active {
  cursor: grabbing;
}

.puzzle-captcha-slider-handle.is-success {
  background-color: #52c41a;
  color: #fff;
}

.puzzle-captcha-slider-handle.is-error {
  background-color: #ff4d4f;
  color: #fff;
}

.puzzle-captcha-slider-text {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #666;
  font-size: 14px;
  user-select: none;
  z-index: 1;
  transition: color 0.3s;
}

.puzzle-captcha-slider-text.is-success {
  color: #52c41a;
}

.puzzle-captcha-slider-text.is-error {
  color: #ff4d4f;
}

.puzzle-captcha-status {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.9);
  padding: 8px 16px;
  border-radius: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  z-index: 3;
  opacity: 0;
  animation: fadeIn 0.3s ease-in-out forwards;
}

.puzzle-captcha-status-icon {
  margin-right: 8px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.puzzle-captcha-status-icon.fa-check-circle {
  background-color: #52c41a;
  color: #fff;
}

.puzzle-captcha-status-icon.fa-times-circle {
  background-color: #ff4d4f;
  color: #fff;
}

.puzzle-captcha-status-text {
  font-size: 14px;
  color: #333;
}

/* 动画效果 */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes zoomIn {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
</style>
